# Carga de librerías
if (!require(summarytools)){
install.packages('summarytools', repos='http://cran.us.r-project.org')
library(summarytools)
}
## Loading required package: summarytools
if(!require(tidyverse)){
install.packages('tidyverse', repos='http://cran.us.r-project.org')
library(tidyverse)
}
## Loading required package: tidyverse
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ──
## ✓ ggplot2 3.3.5 ✓ purrr 0.3.4
## ✓ tibble 3.1.5 ✓ dplyr 1.0.7
## ✓ tidyr 1.1.4 ✓ stringr 1.4.0
## ✓ readr 2.0.2 ✓ forcats 0.5.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
## x tibble::view() masks summarytools::view()
if(!require(caret)){
install.packages('caret', repos='http://cran.us.r-project.org')
library(caret)
}
## Loading required package: caret
## Loading required package: lattice
##
## Attaching package: 'caret'
## The following object is masked from 'package:purrr':
##
## lift
if(!require(corrplot)){
install.packages('corrplot', repos='http://cran.us.r-project.org')
library(corrplot)
}
## Loading required package: corrplot
## corrplot 0.92 loaded
if(!require(wordcloud)){
install.packages('wordcloud', repos='http://cran.us.r-project.org')
library(wordcloud)
}
## Loading required package: wordcloud
## Loading required package: RColorBrewer
if(!require(Rcpp)){
install.packages('Rcpp', repos='http://cran.us.r-project.org')
library(Rcpp)
}
## Loading required package: Rcpp
if(!require(xlsx)){
install.packages('xlsx', repos='http://cran.us.r-project.org')
library(xlsx)
}
## Loading required package: xlsx
if(!require(plyr)){
install.packages('plyr', repos='http://cran.us.r-project.org')
library(plyr)
}
## Loading required package: plyr
## ------------------------------------------------------------------------------
## You have loaded plyr after dplyr - this is likely to cause problems.
## If you need functions from both plyr and dplyr, please load plyr first, then dplyr:
## library(plyr); library(dplyr)
## ------------------------------------------------------------------------------
##
## Attaching package: 'plyr'
## The following objects are masked from 'package:dplyr':
##
## arrange, count, desc, failwith, id, mutate, rename, summarise,
## summarize
## The following object is masked from 'package:purrr':
##
## compact
if(!require(BSDA)){
install.packages('BSDA', repos='http://cran.us.r-project.org')
library(BSDA)
}
## Loading required package: BSDA
##
## Attaching package: 'BSDA'
## The following object is masked from 'package:datasets':
##
## Orange
if(!require(leaflet)){
install.packages('leaflet', repos='http://cran.us.r-project.org')
library(leaflet)
}
## Loading required package: leaflet
if(!require(tm)){
install.packages('tm', repos='http://cran.us.r-project.org')
library(tm)}
## Loading required package: tm
## Loading required package: NLP
##
## Attaching package: 'NLP'
## The following object is masked from 'package:ggplot2':
##
## annotate
Madrid se ha convertido en uno de los destinos predilectos de la inversión inmobiliaria mexicana. El idioma, los lazos culturales entre ambos países, la oferta gastronómica, así como la seguridad jurídica y ciudadana, entre otros atractivos, han provocado el interés del capital inversor mexicano en la capital de España.
Una conocida firma de inversión inmobiliaria mexicana ha pedido realizar un estudio sobre la situación del mercado inmobiliario en la ciudad de Madrid. El problema a resolver es sencillo: ¿Dónde invertir?
El inversor quiere respuesta a estas preguntas:
Es un hecho conocido que el mercado de alquiler se encuentra en franco retroceso debido al auge de los alquileres turísticos. Los propietarios han cambiado el alquiler tradicional por el turístico, espoleado por compañías de impacto mundial como AirBnB. El efecto sobre el alquiler tradicional ha sido nefasto. Por un lado, la oferta de alquileres se ha reducido, lo que ha tenido un gran impacto en los precios, y por otro lado ha generado un flujo de habitantes hacia las zonas periféricas de la capital. El centro de la ciudad es de los turistas.
Debido a esto, y para dar respuesta a nuestro inversor mexicano, se utilizan datos de AirBnB en Madrid.
De todos los datos disponibles en este repositorio se utilizan los siguientes datasets:
listings.csv: contiene información sobre los anuncios de alojamientos turísticos de Airbnb en Madrid (Ver diccionario de datos)
reviews_detailed.csv: detalle de las reseñas por anuncio.
También se utilizará el siguiente dataset:
El mejor enfoque para poder proporcionar una respuesta adecuada a un problema complejo, es dividir el problema en partes más pequeñas, más sencillas de responder, es por ello que se definen una serie de preguntas más específicas. Estas preguntas se hacen desde dos ángulos distintos: por un lado queremos analizar el aspecto geográfico, ¿dónde invertir?, y por otro lado lo concerniente al tipo de alojamiento a comprar ¿Qué comprar y para qué?.
Un poco de geografía: La ciudad de Madrid se divide en 21 distritos. La presencia de la carretera de circunvalación M-30 actúa como barrera geográfica entre los distritos de la almendra central (interior) de los del extrarradio. Dentro de la almendra central se encuentran los siguientes distritos (entre corchetes el precio de alquiler tradicional según el portal [Idealista] a Noviembre de 2020 (https://www.idealista.com/sala-de-prensa/informes-precio-vivienda/alquiler/madrid-comunidad/madrid-provincia/madrid/)): Centro [17,5 euros/m2], Tetuán [15,0 euros/m2], Chamartín [15,4 euros/m2], Chamberí [17,1 euros/m2], Salamanca [17,5 euros/m2], Retiro [15,0 euros/m2] y Arganzuela [14,6 euros/m2].
En el extrarradio tenemos los siguientes distritos: Barajas [11,4 euros/m2], Carabanchel [11,7 euros/m2], Ciudad Lineal [12,8 euros/m2], Fuencarral [15,4 euros/m2],Hortaleza [12,5 euros/m2], Latina [11,8 euros/m2], Moncloa [14,3 euros/m2], Moratalaz [10,9 euros/m2], Puente de Vallecas [12,1 euros/m2], San Blas [11,4 euros/m2], Usera [11,5 euros/m2], Vicálvaro [10,4 euros/m2], Villa de Vallecas [11,2 euros/m2], Villaverde [10,9 euros/m2].
Estas son las preguntas relativas a la geografía de Madrid:
¿Qué distritos tienen más alojamientos?
¿Qué tipo de alojamiento es el más frecuente por distrito?
¿Existen diferencias significativas de precio para los diferentes distritos?
¿Cúal es la densidad de alojamientos por distrito? ¿Qué distritos tienen una mayor densidad de alojamientos por habitante?
¿Existe una diferencia significativa entre los tipos de alojamiento por distrito?
Airbnb oferta 4 tipos de alojamientos: alojamiento completo, habitación privada, habitación compartida y habitación de hotel.Las preguntas a responder en este grupo son:
¿Qué tipo de alojamiento es el más frecuente?
¿Cuál es el precio medio de cada tipo de alojamiento?
¿Cuáles son las palabras más utilizadas en los títulos de los anuncios de alojamientos?
¿Cuantos alojamientos por tipo de habitación y precio hay? ¿Existen diferencias significativas de precio para los diferentes tipos de habitación?
¿Existe una diferencia significativa entre el promedio de reseñas por mes para los alojamientos de tipo habitación privada y apartamento?
¿Cómo es la estacionalidad en el alquiler de alojamientos turísticos?
¿Se podría construir un modelo para predecir el precio del alojamiento en función de otras variables?
Para responder a todas estas preguntas se usan técnicas de ciencia de datos. Todo comienza con la carga de los mismos.
# Limpiamos el workspace
rm(list = ls())
# Cargamos datasets
df_listings <- read.csv('listings.csv', encoding = "UTF-8")
df_reviews <- read.csv('reviews_detailed.csv', header = TRUE)
df_population <- read.xlsx('C5000121.xls',sheetIndex=1, startRow=11, endRow=31, as.data.frame=TRUE, header=FALSE, colIndex=c(2,3))
colnames(df_population) <- c('neighbourhood_group', 'population')
Se carga el dataset y se visualiza su estructura.
print(dfSummary(df_listings), method = 'render')
| No | Variable | Stats / Values | Freqs (% of Valid) | Graph | Valid | Missing | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | id [integer] |
|
19618 distinct values | 19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2 | name [character] |
|
|
19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 3 | host_id [integer] |
|
11325 distinct values | 19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 4 | host_name [character] |
|
|
19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5 | neighbourhood_group [character] |
|
|
19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 6 | neighbourhood [character] |
|
|
19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 7 | latitude [numeric] |
|
7536 distinct values | 19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 8 | longitude [numeric] |
|
7595 distinct values | 19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 9 | room_type [character] |
|
|
19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 10 | price [integer] |
|
544 distinct values | 19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 11 | minimum_nights [integer] |
|
81 distinct values | 19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 12 | number_of_reviews [integer] |
|
435 distinct values | 19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 13 | last_review [character] |
|
|
19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 14 | reviews_per_month [numeric] |
|
682 distinct values | 13981 (71.3%) | 5637 (28.7%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 15 | calculated_host_listings_count [integer] |
|
51 distinct values | 19618 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 16 | availability_365 [integer] |
|
366 distinct values | 19618 (100.0%) | 0 (0.0%) |
Generated by summarytools 1.0.0 (R version 4.1.1)
2021-12-26
El Data Frame Summary muestra la siguiente información:
También se muestra las estadísticas de cada variable, las frecuencias de los valores de las variables, gráficos con la distribución de frecuencias, y número y porcentaje de valores válidos de cada variable. Salvo para la variable reviews_per_month, los valores válidos son el 100% para el resto de las variables.
Tenemos 6 variables discretas de tipo character:
name: descripción del alojamiento.
host_name: nombre del anfitrión (por lo general, sólo el nombre).
neighbourhood_group: distrito. Toma 21 valores diferentes. Se observa que el 44.1% de los alojamientos están en el distrito Centro.
neighbourhood: barrio. Toma 128 valores diferentes.
room_type: tipo de alojamiento Toma 4 valores diferentes. Se observa que el 57.7% es para apartamento completo (Entire home/apt) y el 39.8% para habitación privada (Private room).
last_review: fecha de la ultima reseña. La cadena vacía representa que no hay reseña.
Tenemos 10 variables numéricas:
id: identificador del anuncio.
host_id: identificador del anfitrión.
latitude: latitud según el Sistema Geodésico Mundial (WGS84).
longitude: longitud según el Sistema Geodésico Mundial (WGS84).
price: precio diario en moneda local.
Se observa que el precio mínimo es 0, y el máximo 9999, lo que podría estar indicando que la ausencia de valores se ha codificado como 0 ó 9999, dependiendo del caso. Estos valores aparecerán como outliers (valores extremos).
minimum_nights: cantidad mínima de noches de estancia para el alojamiento (las reglas del calendario pueden ser diferentes). El valor máximo toma un valor excesivamente alto (1125), lo que podría estar indicando una captura errónea del dato. Estos valores aparecerán como outliers.
number_of_reviews: número de reseñas del anuncio.
reviews_per_month: número de reseñas que tiene el anuncio durante su vida útil
calculated_host_listings_count: Número de casas / apartamentos completos que tiene el anfitrión en el scrape actual, en la geografía de la ciudad / región. Observamos que el anfitrión con más alojamientos tiene 163.
availability_365: Disponibilidad 365 días según determine el calendario. Hay que tener en cuenta que un anuncio puede no estar disponible porque ha sido reservado por un invitado o bloqueado por el anfitrión.
Es el principal dataset que se va a emplear en este análisis.
print(dfSummary(df_reviews), method = 'render')
| No | Variable | Stats / Values | Freqs (% of Valid) | Graph | Valid | Missing | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | listing_id [integer] |
|
13981 distinct values | 625006 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2 | id [integer] |
|
625006 distinct values | 625006 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 3 | date [character] |
|
|
625006 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 4 | reviewer_id [integer] |
|
564931 distinct values | 625006 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 5 | reviewer_name [character] |
|
|
625006 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 6 | comments [character] |
|
|
625002 (100.0%) | 4 (0.0%) |
Generated by summarytools 1.0.0 (R version 4.1.1)
2021-12-26
Hay 3 variables discretas de tipo character:
date: fecha de la reseña.
reviewer_name: nombre del cliente que deja la reseña.
comments : comentarios del cliente.
Y 3 variables numéricas:
listing_id : identificador del anuncio.
id: identificador de la reseña.
reviewer_id: identificador del cliente que deja la reseña.
Este dataset se utilizará para analizar la evolución temporal del alquiler turístico.
print(dfSummary(df_population), method = 'render')
| No | Variable | Stats / Values | Freqs (% of Valid) | Graph | Valid | Missing | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | neighbourhood_group [character] |
|
|
21 (100.0%) | 0 (0.0%) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2 | population [numeric] |
|
21 distinct values | 21 (100.0%) | 0 (0.0%) |
Generated by summarytools 1.0.0 (R version 4.1.1)
2021-12-26
Hay 1 variable discreta de tipo character:
Y 1 variable numérica:
Este dataset se empleará para calcular la densidad de población por alojamiento turístico.
Una vez cargados los datos y analizado el contenido de cada dataset, se procede a su limpieza y normalización.
# Guardamos número de filas antes de la limpieza
dim_ini <- nrow(df_listings)
Se comprueba la existencia de elementos vacíos.
# ‘Not Available’ / Missing Values
colSums(is.na(df_listings))
## id name
## 0 0
## host_id host_name
## 0 0
## neighbourhood_group neighbourhood
## 0 0
## latitude longitude
## 0 0
## room_type price
## 0 0
## minimum_nights number_of_reviews
## 0 0
## last_review reviews_per_month
## 0 5637
## calculated_host_listings_count availability_365
## 0 0
Se observa que reviews_per_month es la única variable a la que le faltan valores. Que no haya reseñas no parece ser un error y se puede considerar un valor legítimo, por lo que se sustituyen los valores no disponibles por 0.
df_listings$reviews_per_month[which(is.na(df_listings$reviews_per_month))] <- 0
# Comprobamos que tras la sustitución ya no hay elementos vacíos
colSums(is.na(df_listings))
## id name
## 0 0
## host_id host_name
## 0 0
## neighbourhood_group neighbourhood
## 0 0
## latitude longitude
## 0 0
## room_type price
## 0 0
## minimum_nights number_of_reviews
## 0 0
## last_review reviews_per_month
## 0 0
## calculated_host_listings_count availability_365
## 0 0
Se comprueba si existen elementos con valores en blanco.
colSums(df_listings=="")
## id name
## 0 3
## host_id host_name
## 0 527
## neighbourhood_group neighbourhood
## 0 0
## latitude longitude
## 0 0
## room_type price
## 0 0
## minimum_nights number_of_reviews
## 0 0
## last_review reviews_per_month
## 5637 0
## calculated_host_listings_count availability_365
## 0 0
Existen elementos con valores en blanco (name, host_name y last_review), pero no los vamos a tratar porque estas variables no se van a utilizar en el análisis posterior, a excepción de name, pero el hecho de que tenga tres valores en blanco no afecta al estudio.
Se comprueba si existen elementos con valores iguales a cero.
colSums(df_listings==0)
## id name
## 0 0
## host_id host_name
## 0 0
## neighbourhood_group neighbourhood
## 0 0
## latitude longitude
## 0 0
## room_type price
## 0 8
## minimum_nights number_of_reviews
## 0 5637
## last_review reviews_per_month
## 0 5637
## calculated_host_listings_count availability_365
## 0 5494
Las variables number_of_reviews, reviews_per_month, availability_365 y price tienen valores a cero.
Para number_of_reviews, 0 se puede considerar un valor legítimo (no hay reseñas).
reviews_per_month la tratamos anteriormente y se determinó que 0 se podía considerar un valor legítimo. Si no hay reseñas, el acumulado está también a cero.
En el caso de availability_365, tal y como indicaba el diccionario de datos, hay que tener en cuenta que un anuncio puede no estar disponible porque ha sido reservado por un invitado o bloqueado por el anfitrión, por lo que cero se puede considerar un valor legítimo.
Sin embargo, para price, el valor cero no parece tener sentido, ya que se estaría hablando de alojamiento gratuito. En este caso, no se tendrán en cuenta los registros con price=0.
dim(df_listings)
## [1] 19618 16
df_listings <- df_listings[!(df_listings$price == 0),]
# Comprobamos que se han eliminado registros
dim(df_listings)
## [1] 19610 16
En la descripción del dataset, se observan dos variables con valores que llamaban la atención por tomar valores muy altos: price y minimum_nights.
price: el precio máximo es 9999, lo que podría estar indicando que la ausencia de valores se ha podido codificar como 9999 o que se trata de un error en el scrape. Estos valores aparecerán como outliers.
ggplot() +
geom_boxplot(aes(y = df_listings$price), fill='#4271AE', outlier.colour='red', alpha=0.9) +
scale_x_discrete( ) +
labs(title = "Precio diario en euros", y = "Precio") +
coord_flip()
Se eliminan los outliers por distrito. Para ello se determina un límite de precio máximo en el percentil 95 de los precios de cada distrito, y se eliminan todos los registros por encima de ese valor.
# Se define una función para hacer la limpieza
clean_price <- function(district_name){
district <- df_listings[df_listings$neighbourhood_group==district_name,]
district.quantile <- quantile(district$price, probs=c(.95), na.rm=T)
df_listings <- df_listings[df_listings$neighbourhood_group!=district_name | (df_listings$neighbourhood_group==district_name & df_listings$price<district.quantile),]
return(df_listings)
}
dim(df_listings)
## [1] 19610 16
# Se limpian todos los distritos
df_listings = clean_price("Centro")
df_listings = clean_price("Arganzuela")
df_listings = clean_price("Retiro")
df_listings = clean_price("Salamanca")
df_listings = clean_price("Chamartín")
df_listings = clean_price("Tetuán")
df_listings = clean_price("Chamberí")
df_listings = clean_price("Fuencarral - El Pardo")
df_listings = clean_price("Moncloa - Aravaca")
df_listings = clean_price("Latina")
df_listings = clean_price("Carabanchel")
df_listings = clean_price("Usera")
df_listings = clean_price("Puente de Vallecas")
df_listings = clean_price("Moratalaz")
df_listings = clean_price("Ciudad Lineal")
df_listings = clean_price("Hortaleza")
df_listings = clean_price("Villa de Vallecas")
df_listings = clean_price("Vicálvaro")
df_listings = clean_price("San Blas - Canillejas")
df_listings = clean_price("Barajas")
df_listings = clean_price("Villaverde")
dim(df_listings)
## [1] 18599 16
Tras la limpieza se obtiene el siguiente boxplot:
ggplot() +
geom_boxplot(aes(y = df_listings$price), fill='#4271AE', outlier.colour='red', alpha=0.9) +
scale_x_discrete( ) +
labs(title ="Precio diario en euros", y = "Precio") +
coord_flip()
Obtenemos el precio medio por distrito y ordenamos por precio.
# Obtenemos precio medio por distrito
df_mean_price_by_neighbourhood_group <- aggregate(df_listings$price, list(df_listings$neighbourhood_group), FUN=mean)
# Ordenamos por precio
df_mean_price_by_neighbourhood_group[order(df_mean_price_by_neighbourhood_group[,2]),]
## Group.1 x
## 13 Puente de Vallecas 38.64444
## 21 Villaverde 39.23810
## 18 Usera 42.15789
## 3 Carabanchel 46.45455
## 10 Latina 47.01560
## 20 Villa de Vallecas 53.80645
## 1 Arganzuela 54.41459
## 12 Moratalaz 59.41441
## 7 Ciudad Lineal 60.93496
## 17 Tetuán 66.44890
## 2 Barajas 69.46584
## 8 Fuencarral - El Pardo 69.82095
## 14 Retiro 70.05096
## 11 Moncloa - Aravaca 72.18821
## 4 Centro 75.26788
## 6 Chamberí 76.22035
## 5 Chamartín 79.75410
## 9 Hortaleza 85.80290
## 15 Salamanca 94.23665
## 19 Vicálvaro 96.04412
## 16 San Blas - Canillejas 242.86017
Se observa que Puente de Vallecas es el más barato y que San Blas - Canillejas y Vicálvaro son los más caros. Estos resultados son bastante extraños, ya que esas no son las zonas más caras de la capital.
¿Qué ocurre con San Blas - Canillejas y Vicálvaro?
df_listings %>%
ggplot(aes(x=neighbourhood_group, y=price, fill=neighbourhood_group))+
geom_boxplot(show.legend=FALSE)+
coord_flip()
En el diagrama de cajas se aprecia un gran número de valores extremos en el distrito de San Blas-Canillejas. Hay un motivo principal para esta distribución tan anómala, que también afecta en menor medida a Vicálvaro , Moratalaz, Hortaleza, Fuencarral - El Pardo, Ciudad Lineal, y Barajas, y es su cercanía al estadio Wanda Metropolitano, ya que debido a la celebración de la final de la Champions League en ese estadio en 2019 los precios se dispararon en la zona. Este hecho se ve reflejado en la variable price, que contiene el mayor precio al que se alquila (o alquiló) el alojamiento turístico.
Para solucionar este problema vamos a considerar valores extremos en estos distritos a cualquier valor por encima de 150 euros.
df_listings <- df_listings[!(df_listings$neighbourhood_group=="San Blas - Canillejas" & df_listings$price>150),]
df_listings <- df_listings[!(df_listings$neighbourhood_group=="Vicálvaro" & df_listings$price>150),]
df_listings <- df_listings[!(df_listings$neighbourhood_group=="Moratalaz" & df_listings$price>150),]
df_listings <- df_listings[!(df_listings$neighbourhood_group=="Hortaleza" & df_listings$price>150),]
df_listings <- df_listings[!(df_listings$neighbourhood_group=="Fuencarral - El Pardo" & df_listings$price>150),]
df_listings <- df_listings[!(df_listings$neighbourhood_group=="Ciudad Lineal" & df_listings$price>150),]
df_listings <- df_listings[!(df_listings$neighbourhood_group=="Barajas" & df_listings$price>150),]
# Obtenemos precio medio por distrito
df_mean_price_by_neighbourhood_group <- aggregate(df_listings$price, list(df_listings$neighbourhood_group), FUN=mean)
# Ordenamos por precio
df_mean_price_by_neighbourhood_group[order(df_mean_price_by_neighbourhood_group[,2]),]
## Group.1 x
## 19 Vicálvaro 36.86792
## 13 Puente de Vallecas 38.64444
## 21 Villaverde 39.23810
## 12 Moratalaz 40.02885
## 18 Usera 42.15789
## 7 Ciudad Lineal 43.37610
## 3 Carabanchel 46.45455
## 10 Latina 47.01560
## 2 Barajas 48.69178
## 8 Fuencarral - El Pardo 50.25092
## 16 San Blas - Canillejas 50.75503
## 20 Villa de Vallecas 53.80645
## 1 Arganzuela 54.41459
## 9 Hortaleza 54.67213
## 17 Tetuán 66.44890
## 14 Retiro 70.05096
## 11 Moncloa - Aravaca 72.18821
## 4 Centro 75.26788
## 6 Chamberí 76.22035
## 5 Chamartín 79.75410
## 15 Salamanca 94.23665
Se aprecia tras corregir como los distritos situados dentro de la almendra central son los más caros, con la excepción de Moncloa - Aravaca que es una de las zonas de lujo de la capital.
ggplot() +
geom_boxplot(aes(y = df_listings$price), fill='#4271AE', outlier.colour='red', alpha=0.9) +
scale_x_discrete( ) +
labs(title ="Precio diario en euros", y = "Precio") +
coord_flip()
minimum_nights: el valor máximo toma un valor excesivamente alto (1125), lo que podría estar indicando una captura errónea del dato. Estos valores aparecerán como outliers.
ggplot() +
geom_boxplot(aes(y = df_listings$minimum_nights), fill='#4271AE', outlier.colour='red', alpha=0.9) +
scale_x_discrete( ) +
labs(title = "Número mínimo de noches", y = "Número de noches") +
coord_flip()
El Plan Especial de Hospedaje (PEH), que entró en vigor tras su aprobación definitiva en el Pleno municipal del 27 de marzo de 2019, limita a 90 días la posibilidad de alquilar una vivienda con fines turísticos sin permiso y a partir de ese plazo obliga a obtener una licencia de uso terciario de hospedaje. Esta medida afecta al 95% de los pisos de uso turístico.
Se decide no eliminar registros, en este caso de valores superiores a 90, sino limitar estos registros a un máximo de 90 noches al año.
df_listings$minimum_nights[df_listings$minimum_nights > 90] <- 90
Tras la limpieza se obtiene el siguiente boxplot:
ggplot() +
geom_boxplot(aes(y = df_listings$minimum_nights), fill='#4271AE', outlier.colour='red', alpha=0.9) +
scale_x_discrete( ) +
labs(title = "Número mínimo de noches", y = "Número de noches") +
coord_flip()
Los outliers se consideran válidos al haber propietarios que requieran mayor estabilidad en el alquiler.
Tras el proceso de limpieza, tenemos el siguiente balance:
num_deleted_rows <- dim_ini - nrow(df_listings)
print(sprintf("El número total de filas eliminadas es %d, lo que representa el %.2f %% del total de los datos del dataset.", num_deleted_rows, num_deleted_rows*100/dim_ini))
## [1] "El número total de filas eliminadas es 1341, lo que representa el 6.84 % del total de los datos del dataset."
Se ha eliminado un porcentaje bajo de registros. Se entiende que los outliers se han debido a problemas en el scraping al generar el dataset.
# Guardamos número de filas antes de la limpieza
dim_ini <- nrow(df_reviews)
Se comprueba si existen elementos vacíos.
# ‘Not Available’ / Missing Values
colSums(is.na(df_reviews))
## listing_id id date reviewer_id reviewer_name
## 0 0 0 0 0
## comments
## 4
Se eliminan las reseñas sin comentarios.
dim(df_reviews)
## [1] 625006 6
df_reviews <- df_reviews[!is.na(df_reviews$comments),]
# Comprobamos que se han eliminado registros
dim(df_reviews)
## [1] 625002 6
Se comprueba la existencia de elementos con valores en blanco.
colSums(df_reviews=="")
## listing_id id date reviewer_id reviewer_name
## 0 0 0 0 1
## comments
## 329
Se eliminan las reseñas con valores en blanco.
dim(df_reviews)
## [1] 625002 6
df_reviews <- df_reviews[!(df_reviews$comments == ""),]
# Comprobamos que se han eliminado registros
dim(df_reviews)
## [1] 624673 6
Se observa un registro con reviewer_name en blanco, pero no se trata porque no afecta al estudio.
Se decide no hacer este estudio por la naturaleza de los datos (identificadores, fechas y texto libre).
Tras el proceso de limpieza, se tiene el siguiente balance:
num_deleted_rows <- dim_ini - nrow(df_reviews)
print(sprintf("El número total de filas eliminadas es %d, lo que representa el %.2f %% del total de los datos del dataset.", num_deleted_rows, num_deleted_rows*100/dim_ini))
## [1] "El número total de filas eliminadas es 333, lo que representa el 0.05 % del total de los datos del dataset."
Se limpia el dataframe para posteriormente poder unir esos datos con los de df_listing usando neighbourhood_group.
# Eliminamos los cuatro primeros caracteres del campo neighbourhood_group
df_population$neighbourhood_group <- substring(df_population$neighbourhood_group, first = 5)
# Reemplazamos los guiones por guiones separados por espacios
df_population$neighbourhood_group <- gsub('-',' - ',df_population$neighbourhood_group)
tail(df_population)
## neighbourhood_group population
## 16 Hortaleza 193228
## 17 Villaverde 154808
## 18 Villa de Vallecas 114733
## 19 Vicálvaro 75485
## 20 San Blas - Canillejas 160258
## 21 Barajas 50077
La tercera fase del ciclo de vida de los datos, después de su captura y tratamiento es su análisis con el objeto de extraer información de los mismos.
Para contestar a las diferentes preguntas se requieren distintos tipos de análisis. A continuación, se muestra una tabla con los diferentes análisis a realizar:
| Pregunta | Tipo de análisis | Variables analizadas | Visualización |
|---|---|---|---|
| ¿Qué distritos tienen más alojamientos? | Univariante | listing.csv: neighbourhood_group | Plot |
| ¿Qué tipo de alojamiento es el más frecuente? | Univariante | listing.csv: room_type | Plot |
| ¿Cuáles son las palabras más utilizadas en el título de los anuncios de alojamientos? | Univariante | listing.csv: name | Nube de palabras |
| ¿Cuál es el precio medio para cada tipo de alojamiento? | Bivariante | listing.csv: price, room_type | Tabla |
| ¿Qué tipo de alojamiento es el más frecuente por distrito? | Bivariante | listing.csv: neighbourhood_group, room_type | Plot |
| ¿Existen diferencias significativas de precio para los diferentes distritos? | Bivariante. Test de normalidad, test de homocedasticidad, test de hipótesis. | listing.csv: id, neighbourhood_group, price, price_group (categorización de price) | Tabla, Plot |
| ¿Cuantos alojamientos por tipo de habitación y precio hay? ¿Existen diferencias significativas de precio para los diferentes tipos de alojamiento? | Bivariante. Test de normalidad, test de homocedasticidad, test de hipótesis. | listing.csv: room_type, price, price_group (categorización de price) | Plot |
| ¿Cuál es la densidad de alojamientos por distrito? ¿Qué distritos tienen una mayor densidad de alojamientos por habitante? | Multivariante | C5000121.xls:neighbourhood_group, population - listing.csv: neighbourhood_group, longitude,latitude | Plot, Diagrama de densidad |
| ¿Existe una diferencia significativa entre el promedio de reseñas por mes para los alojamientos de tipo habitación privada y apartamento? | Bivariante: Test de normalidad, test de homocedasticidad, test de hipótesis. | listing.csv: reviews_per_month, room_type | |
| ¿Existe una diferencia significativa entre los tipos de alojamiento por distrito? | Bivariante: test chi cuadrado | listing.csv: neighbourhood_group, room_type | |
| ¿Se podría construir un modelo de regresión para predecir el precio del alojamiento en función de otras variables? | Multivariante | listing.csv: price, minimum_nights, reviews_per_month, availability_365, latitude, longitude | Plot de correlación |
| ¿Cómo es la estacionalidad en el alquiler de alojamientos turísticos? | Univariante | reviews_detailed.csv: date | Plot |
Una vez realizada la limpieza y determinados los datos que se necesitan para el análisis, se guardan.
df_listings <- df_listings[, c("id", "name", "neighbourhood_group", "latitude", "longitude", "room_type", "price", "minimum_nights", "reviews_per_month", "availability_365")]
write.csv(df_listings, "listings_clean.csv")
df_reviews <- df_reviews[, c("listing_id", "date")]
write.csv(df_listings, "reviews_detailed_clean.csv")
# Mostramos plot con distritos ordenados por frecuencia
neighbourhood_group_freq <- data.frame(table(df_listings$neighbourhood_group))
ggplot(neighbourhood_group_freq, aes(x = reorder(Var1, -Freq), y = Freq)) +
geom_segment(aes(x = reorder(Var1, - Freq), xend = reorder(Var1, - Freq), y = 0, yend = Freq), color = "gray", lwd = 1) +
geom_point(size = 6, pch = 21, bg = 4, col = 1) +
geom_text(aes(label=Freq), color = "white", size = 2) +
xlab("Distrito") +
ylab("Número de anuncios") +
coord_flip()+
theme_minimal()
Se observa que el distrito Centro es con mucha diferencia el que más anuncios tiene. Los siguientes distritos Salamanca, Chamberí, Arganzuela y Tetúan son distritos limítrofes al centro. Están todos en el interior de la M-30.
# Mostramos plot con alojamientos ordenados por frecuencia
room_type_freq <- data.frame(table(df_listings$room_type))
ggplot(room_type_freq, aes(x = reorder(Var1, -Freq), y = Freq)) +
geom_segment(aes(x = reorder(Var1, - Freq), xend = reorder(Var1, - Freq), y = 0, yend = Freq), color = "gray", lwd = 1) +
geom_point(size = 8, pch = 21, bg = 4, col = 1) +
geom_text(aes(label=Freq), color = "white", size = 2) +
xlab("Tipo de habitación") +
ylab("Número de anuncios") +
coord_flip()+
theme_minimal()
El tipo de habitación más frecuente es Entire home/apt, seguido de Private room. Los usuarios de Airbnb dan mucha importancia a su privacidad, de ahí los bajos valores de las habitaciones compartidas. El resultado de las habitaciones de hotel es testimonial, ya que este tipo de alojamiento se ofertan en otras plataformas.
Se muestra el resultado con una nube de palabras.
#Generamos el corpus a partir del campo name
wordlist <- strsplit(df_listings$name," ")
wordlist <- as.VCorpus(wordlist)
#Normalizamos los nombres
wordlist <- tm_map(wordlist, tolower)
wordlist <- tm_map(wordlist, removePunctuation)
wordlist <- tm_map(wordlist, removeNumbers)
wordlist <- tm_map(wordlist, removeWords, stopwords(kind='en'))
wordlist <- tm_map(wordlist, removeWords, stopwords(kind='es'))
wordlist <- tm_map(wordlist, PlainTextDocument)
#Creamos el wordcloud
myDTM <- TermDocumentMatrix(wordlist, control = list(minWordLength=1))
m <- as.matrix(myDTM)
v <- sort(rowSums(m), decreasing=TRUE)
wordcloud(names(v),v, min.freq=50, colors=brewer.pal(6,"Dark2"),random.order=FALSE)
Las palabras más utilizadas en los títulos de los anuncios de alojamientos de Airbnb en Madrid son habitación, apartamento y centro, con sus variantes en inglés.
ggplot(df_listings,aes(neighbourhood_group ,fill=room_type)) +
geom_bar() +labs(x="Distrito", y="Tipo de alojamiento") +
guides(fill=guide_legend(title="")) +
ggtitle("Alojamientos por distrito") +
coord_flip()
ggplot(df_listings, aes(fill=room_type, x=neighbourhood_group, y = price))+
geom_bar(position="fill",stat="identity") +
labs(y= 'Alojamiento', x = 'Distrito') +
guides(fill=guide_legend(title=""))+
coord_flip()
Con la excepción de Villaverde, Villa de Vallecas, Vicálvaro, San Blas - Canillejas, Moratalaz,Barajas y Ciudad Lineal, en el resto de Madrid predomina el alquiler del apartamento completo en vez de habitaciones privadas o compartidas.
En los siguientes gráficos se observa la distribución de precios por distrito. Dividimos el precio en 5 grupos: Muy bajo, Bajo, Moderado, Alto y Muy alto.
# Obtenemos cuantiles
quantile_price = integer(10)
for (i in 1:10) {
quantile_price[i] = quantile(df_listings$price,i * 0.1)
}
quantile_price
## [1] 20.6 30.0 36.0 45.0 54.0 65.0 79.0 99.0 135.0 380.0
# Creamos campo nuevo con precio categorizado
df_listings <- df_listings %>% mutate(price_group=ifelse(price < quantile_price[2]+1, "Muy bajo",
ifelse(price < quantile_price[4]+1, "Bajo",
ifelse(price < quantile_price[6]+1, "Moderado",
ifelse(price < quantile_price[8]+1, "Alto", "Muy alto"
)))))
ggplot(df_listings, aes(neighbourhood_group)) +
geom_bar(aes(fill = price_group)) +
ggtitle("Listings per price and neighbourhood group") +
guides(fill=guide_legend(title="")) +
coord_flip()
ggplot(df_listings, aes(x=neighbourhood_group, y=id, fill=price_group ))+
geom_bar(position="fill", stat="identity")+
labs(y="Price", x="Neighbourhood group")+
guides(fill=guide_legend(title=""))+
coord_flip()
En este último gráfico se aprecia la diferencia entre los precios por distritos. Mientras podemos encontrar alojamientos de cualquier tipo de precio en los distritos de la almendra central, los precios más baratos siempre están en el extrarradio.
pal <- colorNumeric(palette=rainbow(6), domain=df_listings$price)
leaflet(data=df_listings)%>%
addProviderTiles(providers$CartoDB.Positron)%>%
addCircleMarkers(~longitude, ~latitude, color=~pal(price), weight=1, radius=1.5, fillOpacity=1, opacity=1,
label=paste("Neighbourhood:", df_listings$neighbourhood_group))%>%
addLegend("bottomright", pal = pal, values =~price,
title="Precio",
opacity=1,)
Se comprueba si el precio sigue una distribución normal cont el test de Kolmogorov-Smirnov:
is_normal_distribution <- function(x){ifelse(x >= 0.05, "Distribution normal", "NO es distribution normal")}
result <- ks.test(df_listings$price, pnorm, mean(df_listings$price), sd(df_listings$price))
## Warning in ks.test(df_listings$price, pnorm, mean(df_listings$price),
## sd(df_listings$price)): ties should not be present for the Kolmogorov-Smirnov
## test
is_normal_distribution(result$p.value)
## [1] "NO es distribution normal"
Como la distribución no es normal, se comprueba la homocedasticidad del precio por distrito con el test de Fligner-Killeen (alternativa no paramétrica, utilizada cuando los datos no cumplen con la condición de normalidad):
is_homogeneous_variance <- function(x){ifelse(x >= 0.05, "Varianza homogénea", "Varianza NO homogénea")}
result <- fligner.test(price ~ neighbourhood_group, data = df_listings)
is_homogeneous_variance(result$p.value)
## [1] "Varianza NO homogénea"
La variable price presenta varianzas estadísticamente diferentes para los diferentes distritos (neighbourhood_group).
La variable price no sigue una distribución normal y presenta varianzas estadísticamente diferentes para los diferentes distritos. Por este motivo, se usa el test de Kruskal-Wallis, que es la alternativa no paramétrica a los contrastes de hipótesis de más de 2 grupos para comprobar si el precio muestra diferencias significativas para los diferentes distritos.
kruskal.test(price ~ neighbourhood_group, data = df_listings)
##
## Kruskal-Wallis rank sum test
##
## data: price by neighbourhood_group
## Kruskal-Wallis chi-squared = 1959.6, df = 20, p-value < 2.2e-16
El p-valor obtenido es menor al nivel de significancia (0.05), por tanto, se puede concluir que el precio (price) muestra diferencias significativas para los diferentes distritos (neighbourhood_group).
mean_price_table <- aggregate(df_listings$price, list(df_listings$room_type), FUN=mean)
# Ordenamos por precio
mean_price_table[order(mean_price_table$x), ]
## Group.1 x
## 4 Shared room 35.37829
## 3 Private room 40.06133
## 1 Entire home/apt 88.40722
## 2 Hotel room 90.42105
ggplot(df_listings, aes(x=price, fill=room_type))+
geom_density(alpha=0.5)
Un alquiler de un apartamento completo tiene un precio similar al de una habitación de hotel, y es un 216% más caro que una habitación privada y un 228% más caro que una habitación compartida.
ggplot(df_listings, aes(room_type)) + geom_bar(aes(fill = price_group)) + ggtitle("Alojamientos por tipo de habitación y precio")
Se aprecia que los apartamentos completos tienen un rango de precios entre alto y muy alto. Las habitaciones privadas suelen ser de precio muy bajo - bajo, siendo las habitaciones compartidas generalmente de precio muy bajo.
Anteriormente, se comprobó que el precio no seguía una distribución normal. Se comprueba ahora la homocedasticidad del precio por tipo de habitación con el test de Fligner-Killeen (alternativa no paramétrica, utilizada cuando los datos no cumplen con la condición de normalidad):
result <- fligner.test(price ~ room_type, data = df_listings)
is_homogeneous_variance(result$p.value)
## [1] "Varianza NO homogénea"
La variable price presenta varianzas estadísticamente diferentes para los diferentes tipos de habitación (room_type).
La variable price no sigue una distribución normal y presenta varianzas estadísticamente diferentes para los diferentes tipos de habitación. Por este motivo, se aplica el test de Kruskal-Wallis, que es la alternativa no paramétrica a los contrastes de hipótesis de más de 2 grupos para comprobar si el precio muestra diferencias significativas para los diferentes tipos de habitación.
kruskal.test(price ~ room_type, data = df_listings)
##
## Kruskal-Wallis rank sum test
##
## data: price by room_type
## Kruskal-Wallis chi-squared = 7606.6, df = 3, p-value < 2.2e-16
El p-valor obtenido es menor al nivel de significancia (0.05), por tanto, se puede concluir que el precio (price) muestra diferencias significativas para los diferentes tipos de habitación (room_type).
Para responder a esta pregunta se necesitan los datos de población por distrito cargados en el dataframe df_population.
Se unen los datos de población a la tabla de frecuencias de anuncios por distrito calculada anteriormente.
# Renombramos columnas de la tabla de frecuencias
colnames(neighbourhood_group_freq) <- c('neighbourhood_group', 'listings')
# Unimos datos de población
neighbourhood_group_freq <- join(neighbourhood_group_freq, df_population, type ='left')
## Joining by: neighbourhood_group
Se crea una nueva variable que almacene el número de habitantes por anuncio.
neighbourhood_group_freq$density <- round(neighbourhood_group_freq$population / neighbourhood_group_freq$listings, 0)
tail(neighbourhood_group_freq)
## neighbourhood_group listings population density
## 16 San Blas - Canillejas 298 160258 538
## 17 Tetuán 773 159849 207
## 18 Usera 266 142454 536
## 19 Vicálvaro 53 75485 1424
## 20 Villa de Vallecas 93 114733 1234
## 21 Villaverde 168 154808 921
Y se visualizan los datos.
ggplot(neighbourhood_group_freq, aes(x = reorder(neighbourhood_group, -density), y = density)) +
geom_segment(aes(x = reorder(neighbourhood_group, - density), xend = reorder(neighbourhood_group, - density), y = 0, yend = density), color = 'gray', lwd = 1) +
geom_point(size = 6, pch = 21, bg = 4, col = 1) +
geom_text(aes(label=density), color = 'white', size = 2) +
xlab('District') +
ylab('Número de habitantes por anuncio y distrito') +
coord_flip()+
theme_minimal()
En el distrito Centro hay un alojamiento por cada 17 habitantes. Los 4 siguientes distritos con menor número de habitantes por alojamiento turísticos están todos en la almendra central. En Vicálvaro hay un alojamiento por cada 1424 habitantes. Todos los distritos con mayor número de habitantes por alojamiento turísticos están en el extrarradio.
Se genera un mapa para visualizar los resultados geográficamente. Se aplica una normalización logarítmica para reducir el rango de valores.
df_listings <- merge(df_listings, neighbourhood_group_freq, type='left')
ggplot(data=df_listings) +
geom_point(aes(x=latitude, y=longitude, color = log10(density))) +
ggtitle('Densidad de alojamientos turísticos por habitante')
El mapa nos permite apreciar la mayor densidad de alojamientos turísticos en la zona central de Madrid.
Se seleccionan los registros para los alojamientos con habitación privada y apartamento completo.
df_aux <- subset(df_listings, room_type=="Private room" | room_type=="Entire home/apt")
head(df_aux)
## neighbourhood_group id
## 1 Arganzuela 42815572
## 2 Arganzuela 13745016
## 3 Arganzuela 10533378
## 4 Arganzuela 16358569
## 5 Arganzuela 40705179
## 6 Arganzuela 47404180
## name latitude longitude
## 1 Tu casa en los Manzanares 40.40578 -3.71919
## 2 Bonito y céntrico piso. Hasta 4 pax 40.41203 -3.72005
## 3 Apartament in Center of Madrid, Atocha 40.40038 -3.69491
## 4 APARTAMENTO EN EL CENTRO 40.40061 -3.69668
## 5 Centric, Spacious & Beautiful 3-Bedrooms Apartment 40.40113 -3.69352
## 6 Delicias I 40.39952 -3.70038
## room_type price minimum_nights reviews_per_month availability_365
## 1 Private room 20 1 0.00 156
## 2 Entire home/apt 50 2 0.17 0
## 3 Entire home/apt 43 2 4.48 165
## 4 Entire home/apt 58 1 0.25 364
## 5 Entire home/apt 108 2 0.25 73
## 6 Entire home/apt 37 2 3.46 171
## price_group listings population density
## 1 Muy bajo 1042 154243 148
## 2 Moderado 1042 154243 148
## 3 Bajo 1042 154243 148
## 4 Moderado 1042 154243 148
## 5 Muy alto 1042 154243 148
## 6 Bajo 1042 154243 148
Se comprueba la normalidad de la distribución para las reseñas por mes (reviews_per_month) con el test de Kolmogorov-Smirnov.
result <- ks.test(df_aux$reviews_per_month, pnorm, mean(df_aux$reviews_per_month), sd(df_aux$reviews_per_month))
## Warning in ks.test(df_aux$reviews_per_month, pnorm,
## mean(df_aux$reviews_per_month), : ties should not be present for the Kolmogorov-
## Smirnov test
is_normal_distribution(result$p.value)
## [1] "NO es distribution normal"
Como la distribución no es normal, se comprueba la homocedasticidad de las reseñas mensuales por tipo de habitación con el test de Fligner-Killeen (alternativa no paramétrica, utilizada cuando los datos no cumplen con la condición de normalidad):
result <- fligner.test(reviews_per_month ~ room_type, data = df_aux)
is_homogeneous_variance(result$p.value)
## [1] "Varianza NO homogénea"
La variable reviews_per_month presenta varianzas estadísticamente diferentes para los diferentes tipos de habitación (room_type).
No se cumplen las condiciones de normalidad, ni de homocedasticidad, por lo que se usa el test de Kruskal-Wallis, que es la alternativa no paramétrica a los contrastes de hipótesis con el objetivo de comprobar si las reseñas por mes muestran diferencias significativas para los tipos de habitación seleccionados.
kruskal.test(reviews_per_month ~ room_type, data = df_aux)
##
## Kruskal-Wallis rank sum test
##
## data: reviews_per_month by room_type
## Kruskal-Wallis chi-squared = 806.91, df = 1, p-value < 2.2e-16
El p-valor obtenido es menor al nivel de significancia (0.05), por tanto, se puede concluir que las reseñas por mes (reviews_per_month) muestran diferencias significativas para los tipos de habitación seleccionados (room_type).
Para comparar si existen diferencias significativas en una variable categórica entre los grupos definidos por otra variable categórica, se puede aplicar el test chi cuadrado, mediante la función chisq.test().
Se construye la tabla de contingencia para ambas variables.
# Tabla de contingencia room_type y neighbourhood_group
conting <- table(df_listings$neighbourhood_group, df_listings$room_type)
conting
##
## Entire home/apt Hotel room Private room Shared room
## Arganzuela 532 1 497 12
## Barajas 43 0 101 2
## Carabanchel 251 0 415 5
## Centro 5691 100 2239 179
## Chamartín 327 4 208 10
## Chamberí 655 8 504 22
## Ciudad Lineal 174 1 387 7
## Fuencarral - El Pardo 104 0 166 1
## Hortaleza 124 5 175 1
## Latina 207 0 359 11
## Moncloa - Aravaca 266 2 257 1
## Moratalaz 23 0 79 2
## Puente de Vallecas 223 0 356 6
## Retiro 367 1 257 3
## Salamanca 865 10 370 10
## San Blas - Canillejas 108 0 187 3
## Tetuán 446 0 319 8
## Usera 84 0 175 7
## Vicálvaro 8 0 45 0
## Villa de Vallecas 22 0 70 1
## Villaverde 32 1 122 13
Se aplica el test chi cuadrado.
chisq.test(conting)
## Warning in chisq.test(conting): Chi-squared approximation may be incorrect
##
## Pearson's Chi-squared test
##
## data: conting
## X-squared = 1835, df = 60, p-value < 2.2e-16
El p-valor obtenido es menor al nivel de significancia (0.05), por tanto, se puede concluir que el tipo de habitación (room_type) muestra diferencias significativas para los diferentes distritos (neighbourhood_group).
Para responder a esta pregunta hay que saber en qué momento del año se han producido los alquileres. Este dato no está disponible, pero se puede utilizar una aproximación sustituyendolo por la fecha en la que se ha hecho una reseña. Para ello se aplica el dataset reviews_detailed.csv que contiene un inventario de todas las reseñas realizadas.
Se convierte el campo date a tipo de datos fecha y se divide la fecha en año, mes, número de día y día de la semana.
reviews_count <- df_reviews$date
reviews_count <- as.Date(reviews_count, "%Y-%m-%d")
reviews_count <- data.frame(date = reviews_count,
year = as.numeric(format(reviews_count, format = "%Y")),
month = as.numeric(format(reviews_count, format = "%m")),
day = as.numeric(format(reviews_count, format = "%d")))
reviews_count$weekday <- strftime(reviews_count$date, '%A')
head(reviews_count)
## date year month day weekday
## 1 2010-03-14 2010 3 14 Sunday
## 2 2010-03-23 2010 3 23 Tuesday
## 3 2010-04-10 2010 4 10 Saturday
## 4 2010-04-21 2010 4 21 Wednesday
## 5 2010-04-26 2010 4 26 Monday
## 6 2010-05-10 2010 5 10 Monday
Se agrupa por año, mes y día de la semana para ver la evolución por cada tipo de variable.
year <- aggregate(reviews_count$date, by = list(reviews_count$year), FUN = length)
colnames(year) <- c("Year","number_of_reviews")
month <- aggregate(reviews_count$date, by = list(reviews_count$month), FUN = length)
colnames(month) <- c("Month","number_of_reviews")
weekday <- aggregate(reviews_count$date, by = list(reviews_count$weekday), FUN = length)
colnames(weekday) <- c("Weekday", "number_of_reviews")
ggplot(reviews_count, aes(x=year)) +
geom_bar(col ='black', fill = 'blue')+
scale_x_discrete(limits = c(2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021)) +
labs (title ="Reseñas por año", x = "Año", y ="Número de alojamientos")
## Warning: Continuous limits supplied to discrete scale.
## Did you mean `limits = factor(...)` or `scale_*_continuous()`?
Como se aprecia en el gráfico, el negocio de los alojamientos turísticos crecía de forma exponencial desde el año 2015. La brusca caída se debe a los efectos del COVID-19 en el año 2020, situación que continua en 2021.
Nota: los últimos datos del dataset son de Abril de 2021, por lo que no se puede apreciar en el gráfico si hay recuperación con respecto a 2020 o no.
ggplot(reviews_count, aes(x=month)) +
geom_bar(col ='black', fill = 'blue')+
scale_x_discrete(limits = c("Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre")) +
labs (title ="Reseñas por mes", x = "Mes", y ="Número de alojamientos")+
coord_flip()
Sorprende que las reseñas se mantienen más o menos constantes a lo largo del año. Aún más que sean los meses de verano donde menos reseñas se escriben.
ggplot(data=reviews_count, aes(x=weekday)) +
geom_bar(col='black', fill ='blue') +
labs (title = "Reseñas por días", x = "Días", y="Número de alojamientos")
Mientras que las reseñas permanecen constante a lo largo de los días de la semana, se ve un fuerte incremento en los domingos y lunes, lo que nos indica que los fines de semana es cuando más se reservan los alojamientos turísticos.
Se busca predecir el precio de alquiler de un alojamiento turístico (variable dependiente cuantitativa) en función de una serie de variables cualitativas y cuantitativas (variables independientes). Para ello se utiliza un modelo de regresión lineal múltiple.
Se ha comprobado antes que la variable price no sigue una distribución normal, afectada sobre todo, por valores extremos debido al lujo. Se utiliza una transformación logarítmica para acercar más su distribución a la normalidad.
df_listings$price_trans <- log2(df_listings$price)
qqnorm(df_listings$price_trans); qqline(df_listings$price_trans)
Tras hacer la transformación, se observa que la nueva variable price_trans se acerca más a una distribución normal.
El modelo de regresión lineal emplea la relación lineal entre dos variables para predecir el valor que va a tomar una en función del valor que toma la otra. La condición para que funcione es que ambas variables estén correlacionadas. El primer paso a la hora de plantear un modelo de regresión lineal es hacer un estudio de correlación.
df_reg <- df_listings[,c("price_trans", "minimum_nights", "reviews_per_month", "availability_365", "latitude", "longitude")]
correlacionMatrix <- cor(df_reg)
corrplot(corr = cor(correlacionMatrix, method = "pearson"), method = "number", tl.cex = 0.7,number.cex = 0.8, cl.pos = "n")
# Generamos un dataset con las variables que van a formar parte del estudio
df_reg <-df_listings[,c("price_trans", "minimum_nights", "reviews_per_month", "availability_365", "latitude", "longitude","neighbourhood_group","room_type")]
Se observa que la correlación entre el precio y el resto de las variables es bajo.
La correlación más alta se da entre price_trans y longitude, pero es muy baja (-0.34).
Se convierten en factores los distintos valores de las variables categóricas, y se usan los valores con mayor peso como valores de referencia. En este caso sería el distrito Centro y el tipo de habitación Entire home/apartment.
df_reg$neighbourhood_group <- as.factor(df_reg$neighbourhood_group)
df_reg$room_type <- as.factor(df_reg$room_type)
df_reg$neighbourhood_group <- relevel(df_reg$neighbourhood_group, ref="Centro")
df_reg$room_type <- relevel (df_reg$room_type, ref="Entire home/apt")
Se crean distintos modelos usando distintas combinaciones de las variables cualitativas y cuantitativas
model1 <- lm(price_trans ~., data=df_reg)
model2 <- lm(price_trans ~ reviews_per_month + room_type + neighbourhood_group, data=df_reg)
model3 <- lm(price_trans ~ reviews_per_month + longitude + latitude + room_type + neighbourhood_group, data=df_reg)
model4 <- lm(price_trans ~ reviews_per_month + longitude + latitude + minimum_nights + room_type + neighbourhood_group, data=df_reg)
model5 <- lm(price_trans ~ minimum_nights + room_type + neighbourhood_group, data=df_reg)
model6 <- lm(price_trans ~ availability_365 + room_type + neighbourhood_group, data=df_reg)
model7 <- lm(price_trans ~ reviews_per_month + room_type + neighbourhood_group +longitude:latitude, data=df_reg)
model8 <- lm(price_trans ~ room_type + neighbourhood_group + reviews_per_month:availability_365, data=df_reg)
model9 <- lm(price_trans ~ room_type + neighbourhood_group, data=df_reg)
La bondad del ajuste se mide con el parámetro Adjusted R-squared, cuándo este valor es próximo a 1 es indicativo de un buen modelo de ajuste. Se utiliza este parámetro para elegir el modelo con el mejor coeficiente.
#Generamos los coeficientes de determinación de cada modelo
tabla.coef <- matrix(c(1, summary(model1)$r.squared,
2, summary(model2)$r.squared,
3, summary(model3)$r.squared,
4, summary(model4)$r.squared,
5, summary(model5)$r.squared,
6, summary(model6)$r.squared,
7, summary(model7)$r.squared,
8, summary(model8)$r.squared,
9, summary(model9)$r.squared),
ncol=2,byrow=TRUE)
colnames(tabla.coef) <-c("Modelo","R-squared")
tabla.coef
## Modelo R-squared
## [1,] 1 0.4512104
## [2,] 2 0.4374237
## [3,] 3 0.4385758
## [4,] 4 0.4486447
## [5,] 5 0.4264732
## [6,] 6 0.4205993
## [7,] 7 0.4374426
## [8,] 8 0.4278817
## [9,] 9 0.4193874
Se elige como mejor modelo a áquel que tiene el coeficiente de determinación mayor. En este caso el modelo 1.
Por último, para profundizar en la calidad del ajuste deben analizarse los residuos que nos indicarán realmente cómo se ajusta nuestro modelo a los datos muestrales.
Para obtener los residuos se aplica la función rstandard sobre el modelo. Esta función devuelve los residuos. A continuación se estudian sus estadísticos
residuos <- model1$residuals
summary(residuos)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -3.08542 -0.51001 -0.06726 0.00000 0.42470 3.66172
boxplot(residuos)
qqnorm(residuos)
qqline(residuos)
Los residuos siguen una distribución aparentemente normal, la distribución de los datos es simétrica respecto a su mediana, y el tamaño de la caja desde el primer al tercer cuartil es también simétrica respecto a la mediana, que está a su vez, centrada en el 0.
Se genera también la gráfica de residuos vs valores ajustados. Esta gráfica muestra los residuos en el eje Y y los valores ajustados en el eje X. Se utiliza para verificar el supuesto de que los residuos están distribuidos aleatoriamente y tienen una varianza constante. Lo ideal es que los puntos se ubiquen aleatoriamente a ambos lados del eje X.
Si existe una dispersión en abanico es síntoma de que la varianza no es constante. Esto también se aprecia en la línea azul que fluctúa a lo largo del eje X.
# Comparamos los valores ajustados con los residuos
ggplot(data=df_reg, aes(model1$fitted.values, model1$residuals)) +
geom_point() +
geom_smooth(color ="blue", se = FALSE)+
geom_hline(yintercept = 0) +
ggtitle('Valores ajustados vs Residuos')+
theme_bw()
## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'
Respecto a la bondad del modelo, se observan estos parámetros:
summary(model1)
##
## Call:
## lm(formula = price_trans ~ ., data = df_reg)
##
## Residuals:
## Min 1Q Median 3Q Max
## -3.0854 -0.5100 -0.0673 0.4247 3.6617
##
## Coefficients:
## Estimate Std. Error t value
## (Intercept) -1.609e+02 2.903e+01 -5.542
## minimum_nights -9.002e-03 4.721e-04 -19.070
## reviews_per_month -1.217e-01 4.528e-03 -26.887
## availability_365 3.543e-04 3.836e-05 9.236
## latitude 4.205e+00 7.163e-01 5.871
## longitude 7.106e-01 6.212e-01 1.144
## neighbourhood_groupArganzuela -1.619e-01 2.699e-02 -5.998
## neighbourhood_groupBarajas -3.013e-01 1.025e-01 -2.940
## neighbourhood_groupCarabanchel -2.854e-01 3.986e-02 -7.160
## neighbourhood_groupChamartín -9.667e-02 4.399e-02 -2.197
## neighbourhood_groupChamberí -6.278e-02 2.664e-02 -2.357
## neighbourhood_groupCiudad Lineal -4.857e-01 4.900e-02 -9.912
## neighbourhood_groupFuencarral - El Pardo -5.765e-01 6.816e-02 -8.458
## neighbourhood_groupHortaleza -4.383e-01 6.911e-02 -6.342
## neighbourhood_groupLatina -3.005e-01 4.197e-02 -7.161
## neighbourhood_groupMoncloa - Aravaca -8.170e-02 3.881e-02 -2.105
## neighbourhood_groupMoratalaz -4.180e-01 8.215e-02 -5.088
## neighbourhood_groupPuente de Vallecas -5.381e-01 4.491e-02 -11.983
## neighbourhood_groupRetiro -3.933e-02 3.530e-02 -1.114
## neighbourhood_groupSalamanca 1.296e-01 2.976e-02 4.353
## neighbourhood_groupSan Blas - Canillejas -3.578e-01 7.449e-02 -4.803
## neighbourhood_groupTetuán -3.204e-01 4.072e-02 -7.868
## neighbourhood_groupUsera -2.967e-01 5.271e-02 -5.629
## neighbourhood_groupVicálvaro -5.011e-01 1.190e-01 -4.210
## neighbourhood_groupVilla de Vallecas -4.452e-02 1.013e-01 -0.440
## neighbourhood_groupVillaverde -2.912e-01 7.554e-02 -3.855
## room_typeHotel room -2.082e-01 6.466e-02 -3.219
## room_typePrivate room -1.201e+00 1.196e-02 -100.363
## room_typeShared room -1.707e+00 4.322e-02 -39.486
## Pr(>|t|)
## (Intercept) 3.03e-08 ***
## minimum_nights < 2e-16 ***
## reviews_per_month < 2e-16 ***
## availability_365 < 2e-16 ***
## latitude 4.41e-09 ***
## longitude 0.252698
## neighbourhood_groupArganzuela 2.04e-09 ***
## neighbourhood_groupBarajas 0.003282 **
## neighbourhood_groupCarabanchel 8.38e-13 ***
## neighbourhood_groupChamartín 0.028011 *
## neighbourhood_groupChamberí 0.018452 *
## neighbourhood_groupCiudad Lineal < 2e-16 ***
## neighbourhood_groupFuencarral - El Pardo < 2e-16 ***
## neighbourhood_groupHortaleza 2.32e-10 ***
## neighbourhood_groupLatina 8.31e-13 ***
## neighbourhood_groupMoncloa - Aravaca 0.035279 *
## neighbourhood_groupMoratalaz 3.65e-07 ***
## neighbourhood_groupPuente de Vallecas < 2e-16 ***
## neighbourhood_groupRetiro 0.265296
## neighbourhood_groupSalamanca 1.35e-05 ***
## neighbourhood_groupSan Blas - Canillejas 1.58e-06 ***
## neighbourhood_groupTetuán 3.80e-15 ***
## neighbourhood_groupUsera 1.84e-08 ***
## neighbourhood_groupVicálvaro 2.56e-05 ***
## neighbourhood_groupVilla de Vallecas 0.660200
## neighbourhood_groupVillaverde 0.000116 ***
## room_typeHotel room 0.001288 **
## room_typePrivate room < 2e-16 ***
## room_typeShared room < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.7387 on 18248 degrees of freedom
## Multiple R-squared: 0.4512, Adjusted R-squared: 0.4504
## F-statistic: 535.8 on 28 and 18248 DF, p-value: < 2.2e-16
Residual standard error: 0.7378 on 18248 degrees of freedom.
Multiple R-squared: 0.4512, Adjusted R-squared: 0.4504
F-statistic: 535.8 on 28 and 18248 DF, p-value: < 2.2e-16.
El RSE es la desviación estandar de los residuos, cuánto menor mejor.
El modelo es capaz de explicar el 45% de la variabilidad observada en los precios
El test F muestra un p-value menor de 0.05 por lo que el modelo en conjunto es significativo.
Se prueba ahora un modelo que sea capaz de calcular la probabilidad de que el precio de alquiler de un alojamiento turístico supere los 100 euros por noche. Para ello discretizamos la variable precio en dos categorías: <= 100 euros y >100 euros
#Creamos la variable dependiente cualitativa dicotómica price_100
df_listings$price_100 <- factor(ifelse(df_listings$price>100,"Yes","No"))
#Preparamos los datos para la regresión
df_reg <-df_listings[,c("price_100", "minimum_nights", "reviews_per_month", "availability_365", "latitude", "longitude","neighbourhood_group","room_type")]
df_reg$neighbourhood_group <- as.factor(df_reg$neighbourhood_group)
df_reg$room_type <- as.factor(df_reg$room_type)
df_reg$neighbourhood_group <- relevel(df_reg$neighbourhood_group, ref="Centro")
df_reg$room_type <- relevel (df_reg$room_type, ref="Entire home/apt")
Creamos distintos modelos usando distintas combinaciones de las variables cualitativas y cuantitativas
#Generamos distintos modelos
logi1 <- glm(formula = price_100 ~ ., data=df_reg, family= binomial(link=logit))
logi2 <- glm(formula = price_100 ~ reviews_per_month + room_type + neighbourhood_group, data=df_reg, family= binomial(link=logit))
logi3 <- glm(formula = price_100 ~ reviews_per_month + longitude + latitude + room_type + neighbourhood_group, data=df_reg, family= binomial(link=logit))
logi4 <- glm(formula = price_100 ~ reviews_per_month + longitude + latitude + minimum_nights + room_type + neighbourhood_group, data=df_reg, family= binomial(link=logit))
logi5 <- glm(formula = price_100 ~ minimum_nights + room_type + neighbourhood_group, data=df_reg, family= binomial(link=logit))
logi6 <- glm(formula = price_100 ~ availability_365 + room_type + neighbourhood_group, data=df_reg, family= binomial(link=logit))
logi7 <- glm(formula = price_100 ~ reviews_per_month + room_type + neighbourhood_group +longitude:latitude, data=df_reg, family= binomial(link=logit))
logi8 <- glm(formula = price_100 ~ room_type + neighbourhood_group + reviews_per_month:availability_365, data=df_reg, family= binomial(link=logit))
logi9 <- glm(formula = price_100 ~ room_type + neighbourhood_group, data=df_reg, family= binomial(link=logit))
La bondad del ajuste se va a medir con el criterio de información de Akaike. El menor de los valores nos indicará cual es el mejor modelo. En este caso el modelo 1.
#Generamos los AIC (criterio de información de Akaike) para comparar los modelos
tabla.coef <- matrix(c(1, summary(logi1)$aic,
2, summary(logi2)$aic,
3, summary(logi3)$aic,
4, summary(logi4)$aic,
5, summary(logi5)$aic,
6, summary(logi6)$aic,
7, summary(logi7)$aic,
8, summary(logi8)$aic,
9, summary(logi9)$aic),
ncol=2,byrow=TRUE)
colnames(tabla.coef) <-c("Modelo","R-squared")
tabla.coef
## Modelo R-squared
## [1,] 1 14435.15
## [2,] 2 14557.66
## [3,] 3 14549.02
## [4,] 4 14481.82
## [5,] 5 14899.55
## [6,] 6 14902.83
## [7,] 7 14556.36
## [8,] 8 14779.33
## [9,] 9 14928.65
Se analiza la precisión del modelo, comparando la predicción del modelo contra un conjunto de prueba (train-test).
#Generamos los datos de entrenamiento y test
set.seed(100)
n<-nrow(df_reg)
train_index <- sample(1:n, size=round(0.8*n), replace=FALSE)
train <- df_reg[train_index,]
test <- df_reg[-train_index,]
#Generamos el modelo
logist1 <- glm(formula = price_100 ~ ., data=train, family= binomial(link=logit))
summary(logist1)
##
## Call:
## glm(formula = price_100 ~ ., family = binomial(link = logit),
## data = train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -1.4042 -0.7126 -0.3543 -0.1831 3.1014
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -4.183e+02 1.307e+02 -3.200 0.001376
## minimum_nights -1.843e-02 2.498e-03 -7.378 1.61e-13
## reviews_per_month -3.998e-01 2.424e-02 -16.497 < 2e-16
## availability_365 1.042e-03 1.661e-04 6.274 3.52e-10
## latitude 9.989e+00 3.240e+00 3.083 0.002052
## longitude -3.768e+00 2.958e+00 -1.274 0.202753
## neighbourhood_groupArganzuela -5.084e-01 1.325e-01 -3.837 0.000125
## neighbourhood_groupBarajas -3.625e-01 5.297e-01 -0.684 0.493786
## neighbourhood_groupCarabanchel -1.135e+00 2.326e-01 -4.880 1.06e-06
## neighbourhood_groupChamartín -4.105e-01 1.918e-01 -2.140 0.032381
## neighbourhood_groupChamberí -1.552e-02 1.073e-01 -0.145 0.884927
## neighbourhood_groupCiudad Lineal -1.158e+00 2.862e-01 -4.045 5.22e-05
## neighbourhood_groupFuencarral - El Pardo -1.782e+00 3.644e-01 -4.891 1.01e-06
## neighbourhood_groupHortaleza -1.330e+00 3.604e-01 -3.692 0.000223
## neighbourhood_groupLatina -5.674e-01 2.104e-01 -2.696 0.007014
## neighbourhood_groupMoncloa - Aravaca 1.042e-01 1.520e-01 0.685 0.493099
## neighbourhood_groupMoratalaz -6.305e-01 5.610e-01 -1.124 0.261045
## neighbourhood_groupPuente de Vallecas -1.642e+00 3.194e-01 -5.140 2.75e-07
## neighbourhood_groupRetiro 3.846e-01 1.448e-01 2.657 0.007891
## neighbourhood_groupSalamanca 5.221e-01 1.219e-01 4.284 1.83e-05
## neighbourhood_groupSan Blas - Canillejas -7.092e-01 3.879e-01 -1.828 0.067519
## neighbourhood_groupTetuán -7.044e-01 1.803e-01 -3.907 9.34e-05
## neighbourhood_groupUsera -4.574e-01 3.023e-01 -1.513 0.130212
## neighbourhood_groupVicálvaro -6.261e-02 8.100e-01 -0.077 0.938385
## neighbourhood_groupVilla de Vallecas 5.251e-01 5.167e-01 1.016 0.309597
## neighbourhood_groupVillaverde -5.449e-01 5.187e-01 -1.051 0.293442
## room_typeHotel room -4.868e-02 2.134e-01 -0.228 0.819542
## room_typePrivate room -2.186e+00 7.187e-02 -30.416 < 2e-16
## room_typeShared room -2.197e+00 2.885e-01 -7.617 2.60e-14
##
## (Intercept) **
## minimum_nights ***
## reviews_per_month ***
## availability_365 ***
## latitude **
## longitude
## neighbourhood_groupArganzuela ***
## neighbourhood_groupBarajas
## neighbourhood_groupCarabanchel ***
## neighbourhood_groupChamartín *
## neighbourhood_groupChamberí
## neighbourhood_groupCiudad Lineal ***
## neighbourhood_groupFuencarral - El Pardo ***
## neighbourhood_groupHortaleza ***
## neighbourhood_groupLatina **
## neighbourhood_groupMoncloa - Aravaca
## neighbourhood_groupMoratalaz
## neighbourhood_groupPuente de Vallecas ***
## neighbourhood_groupRetiro **
## neighbourhood_groupSalamanca ***
## neighbourhood_groupSan Blas - Canillejas .
## neighbourhood_groupTetuán ***
## neighbourhood_groupUsera
## neighbourhood_groupVicálvaro
## neighbourhood_groupVilla de Vallecas
## neighbourhood_groupVillaverde
## room_typeHotel room
## room_typePrivate room ***
## room_typeShared room ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 13779 on 14621 degrees of freedom
## Residual deviance: 11480 on 14593 degrees of freedom
## AIC: 11538
##
## Number of Fisher Scoring iterations: 6
Se aplica el modelo a los datos de test que habíamos generado
#Generamos las predicciones
predicted <- predict(logist1, test, type="response")
predicted <-ifelse(predicted<0.5,"No","Yes")
#Generamos la matriz de confusion
confusionMatrix(test$price_100, as.factor(predicted))
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 2981 17
## Yes 633 24
##
## Accuracy : 0.8222
## 95% CI : (0.8094, 0.8344)
## No Information Rate : 0.9888
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.0487
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 0.82485
## Specificity : 0.58537
## Pos Pred Value : 0.99433
## Neg Pred Value : 0.03653
## Prevalence : 0.98878
## Detection Rate : 0.81560
## Detection Prevalence : 0.82025
## Balanced Accuracy : 0.70511
##
## 'Positive' Class : No
##
La matriz de confusión nos da una exactitud del 82.2%, esto es, la habilidad del modelo para detectar correctamente los alquileres por encima y debajo de 100 euros.
La sensibilidad es del 82.5%, es la habilidad de detectar verdaderos positivos en el total de positivos que hay en la realidad. Consideramos como positivo el que el alquiler sea mayor de 100 euros la noche.
La especificidad es del 58.5%, es la habilidad del modelo de detectar verdaderos negativos en el total de negativos reales. El modelo es solo capaz de predecir el 58.5% de los casos reales donde el precio es mayor de 100 euros.
Atendiendo a las respuestas obtenidas en todas y cada una de las preguntas que se han realizado se llegan a las siguientes conclusiones:
- ¿Qué tipo de vivienda comprar?
Viviendas de segunda mano, de tamaño mediano-pequeño, a reformar.
El hecho de comprar viviendas de segundo mano se explica por la ausencia de promociones de obra nueva en las zonas de interés. Se explica en el siguiente punto cuáles son estas zonas.
Viviendas de tamaño mediano-pequeño, ya que la vivienda preferida por los usuarios de Airbnb son los apartamentos enteros. El precio se incrementa hasta un 250% con respecto a alquilar una habitación compartida. Para compensar esto, se requerirían varias habitaciones privadas en el mismo inmueble, pero implicaría inmuebles más caros de comprar y de reformar.
En el análisis de palabras más utilizadas en los anuncios se observan muchos términos relacionados con la localización y tipo de inmueble, pero también hay adjetivos como: stylish, acogedor, precioso, beautiful, cozy, luxury, lovely, luminosa, spacious, lo que indica una necesidad de transformar los inmuebles de acuerdo a las tendencias de decoración minimalista actuales.
- ¿En qué zona de la capital?
Viviendas en la zona de la almendra central pero fuera del Centro debido a la saturación del mismo (17 habitantes por alojamiento turístico).
Se recomiendan oportunidades en barrios más caros como Chamberí, Salamanca, y Retiro, y centrar los esfuerzos en adquirir imuebles en zonas de la almendra central con menor coste: Arganzuela, Tetuán, Chamartín. Otra opción muy interesante por precio y su proximidad al aeropuerto sería Barajas.
Las oportunidades serían ventas de inmuebles ya destinados al alquiler turístico en el que la inversión por reforma no sería necesaria. Sería posible encontrar estas oportunidades debido a los efectos de la pandemia.
- ¿Qué destino se le va a dar al inmueble?
La inversión debe destinarse al alquiler turístico. Es un mercado que hasta la pandemia de COVID-19 crecía de forma exponencial y al que se ven signos de recuperación.
Según las estadísticas del Ayuntamiento de Madrid la superficie media de la vivienda en Madrid es de 82 m2.
Según el portal idealista el precio medio del alquiler en Madrid en Noviembre de 2021 es de 14,6 euros/metro cuadrado. Por lo tanto, el precio medio del aquiler tradicional es de 1197 euros al mes.
En este ejercicio se ha comprobado que el precio medio del alquiler turístico es de 88 euros/noche, por lo que llegaría a los 2640 euros al mes (considerando 30 días). Un 220% más.
El análisis de estacionalidad del alquiler turístico también nos muestra homogeneidad a lo largo de los meses y una alta ocupación durante todos los días de la semana.
| Contribuciones | Firma |
|---|---|
| Investigación previa | BLB, GRF |
| Redacción de las respuestas | BLB, GRF |
| Desarrollo del código | BLB, GRF |